package org.annoflow.javassist;

import java.util.List;

import org.annoflow.filter.Filter;
import org.annoflow.filter.FilterPoint;
import org.annoflow.policy.PolicyType;
import org.annoflow.policy.Policy;
import org.annoflow.policy.TopSecret;
import org.apache.log4j.Logger;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.Translator;

public class AnnoTranslator implements Translator {
	private AnnoCustomExprEditor cusExpr;
	private Logger logger = Logger.getLogger(AnnoTranslator.class);

	public AnnoTranslator(ConfigParser current) {
		List<CommandInterface> editConfigs = current.getCommands();
		cusExpr = new AnnoCustomExprEditor(editConfigs);
	}

	@Override
	public void onLoad(ClassPool pool, String className)
			throws NotFoundException, CannotCompileException {
		logger.info("Loading Class " + className);
		CtClass clazz = pool.get(className);
		try {
			rewriteFilters(clazz);
			addPolicies(clazz, pool);
			cusExpr.staticEdits(clazz);
			if (!clazz.isFrozen()
					&& clazz.getPackageName().startsWith("dummy.")) {
				clazz.instrument(cusExpr);
			}
		} catch (ClassNotFoundException e) {
			logger.error(e);
		}

	}

	private void rewriteFilters(CtClass clazz) throws ClassNotFoundException {
		try {
			for (CtMethod method : clazz.getDeclaredMethods()) {
				for (Object annotation : method.getAnnotations()) {
					if (annotation instanceof Filter) {
						logger.info("Found Filter annotation on "
								+ method.getName() + " in class "
								+ clazz.getName());
						Filter filterAnno = (Filter) annotation;
						String annoType = filterAnno.type().getName();
						@SuppressWarnings("unchecked")
						Class<FilterPoint> annoClazz = (Class<FilterPoint>)Class
								.forName(annoType);
						FilterPoint fPoint = annoClazz.newInstance();
						String filterCode = fPoint.getFilterCode();
						String returnFilterCode = fPoint.getReturnFilterCode();
						// System.err.println(filterCode);
						//method.insertBefore("org.annoflow.audit.ContextTracker.getInstance().setFilterContext($class);");
						if (filterCode != null)
							method.insertBefore("org.annoflow.audit.ContextTracker.getInstance().setFilterContext($class);"+filterCode+"org.annoflow.audit.ContextTracker.getInstance().clear();");
						if (returnFilterCode != null)
							method.insertAfter("org.annoflow.audit.ContextTracker.getInstance().setFilterContext($class);"+returnFilterCode+"org.annoflow.audit.ContextTracker.getInstance().clear();", true); //Treat as "finally" block
					}
				}
			}
		} catch (IllegalAccessException e) {
			logger.error("Could not call filter default constructor", e);
		} catch (InstantiationException e) {
			logger.error("Exception thrown inside of constructor", e);
		} catch (CannotCompileException e) {
			logger.error("The replacement text is no good!", e);
		}
	}

	private void addPolicies(CtClass clazz, ClassPool pool) {
		try {
			for (Object annotation : clazz.getAnnotations()) {
				String policyClass = null;
				if (annotation instanceof Policy) {
					logger.info("Found Policy annotation on " + clazz.getName()
							+ " class");
					Policy policy = (Policy) annotation;
					policyClass = policy.policy().getName();
				} else if (annotation instanceof TopSecret) {
					policyClass = "org.annoflow.policy.TopSecretPolicy";
					logger.info("Found TopSecret annotation on "
							+ clazz.getName() + " class");
				}

				if (policyClass != null) {
					@SuppressWarnings("unchecked")
					Class<PolicyType> pClazz = (Class<PolicyType>) Class
							.forName(policyClass);
					String src = pClazz.newInstance().getPolicyCode();
					CtConstructor staticConstructor = clazz
							.makeClassInitializer();
					if (staticConstructor != null) {
						staticConstructor.insertBefore(src);
					}
//					for (CtConstructor construct : clazz.getConstructors()) {
//						// System.err.println(src);
//						construct.insertBefore(src);
//					}
				}
			}
			
		} catch (IllegalAccessException e) {
			logger.error("Could not call policy default constructor", e);
		} catch (InstantiationException e) {
			logger.error("Exception thrown inside of constructor", e);
		} catch (ClassNotFoundException e) {
			logger.error("Could not find specified class", e);
		} catch (CannotCompileException e) {
			logger.error(
					"Could not compile the modifications for Policy cutin", e);
		} catch (SecurityException e) {
			logger.error(
					"Could not construct a policy object for the class", e);
		} catch (IllegalArgumentException e) {
			logger.error("The arguments to the constructor are incorrect", e);
		}
	}

	@Override
	public void start(ClassPool pool) throws NotFoundException,
			CannotCompileException {

	}

}
